/*
Copyright (c) 2019 the Octant contributors. All Rights Reserved.
SPDX-License-Identifier: Apache-2.0
*/

package component

import (
	jsoniter "github.com/json-iterator/go"
	"github.com/pkg/errors"

	"github.com/vmware-tanzu/octant/internal/util/json"

	"github.com/vmware-tanzu/octant/pkg/action"
)

// EmptyContentResponse is an empty content response.
var EmptyContentResponse = ContentResponse{}

// ContentResponse is a a content response. It contains a
// title and one or more components.
type ContentResponse struct {
	Title              []TitleComponent `json:"title,omitempty"`
	Components         []Component      `json:"viewComponents"`
	ExtensionComponent Component        `json:"extensionComponent,omitempty"`
	TitleComponents    []Component      `json:"titleComponents,omitempty"`
}

// NewContentResponse creates an instance of ContentResponse.
func NewContentResponse(title []TitleComponent) *ContentResponse {
	return &ContentResponse{
		Title: title,
	}
}

// AddTitleComponents Adds any number of components to accompany the title
func (c *ContentResponse) AddTitleComponents(components ...Component) {
	for i := range components {
		if components[i] != nil {
			c.TitleComponents = append(c.TitleComponents, components[i])
		}
	}
}

// Add adds zero or more components to a content response. Nil components
// will be ignored.
func (c *ContentResponse) Add(components ...Component) {
	for i := range components {
		if components[i] != nil {
			c.Components = append(c.Components, components[i])
		}
	}
}

// SetExtension adds zero or more components to an extension content response.
func (c *ContentResponse) SetExtension(component *Extension) {
	c.ExtensionComponent = component
}

// AddButton adds one or more actions to a content response.
func (c *ContentResponse) AddButton(name string, payload action.Payload, buttonOptions ...ButtonOption) {
	button := NewButton(name, payload, buttonOptions...)
	c.TitleComponents = append(c.TitleComponents, button)
}

// UnmarshalJSON unmarshals a content response from JSON.
func (c *ContentResponse) UnmarshalJSON(data []byte) error {
	stage := struct {
		Title           []TypedObject `json:"title,omitempty"`
		Components      []TypedObject `json:"viewComponents,omitempty"`
		TitleComponents []TypedObject `json:"titleComponents,omitempty"`
	}{}

	if err := json.Unmarshal(data, &stage); err != nil {
		return err
	}

	for _, t := range stage.Title {
		title, err := t.ToComponent()
		if err != nil {
			return err
		}

		titleComponent, ok := title.(TitleComponent)
		if !ok {
			return errors.New("component in title isn't a title view component")
		}

		c.Title = append(c.Title, titleComponent)
	}

	for _, to := range stage.Components {
		vc, err := to.ToComponent()
		if err != nil {
			return err
		}

		c.Components = append(c.Components, vc)
	}

	for _, to := range stage.TitleComponents {
		vc, err := to.ToComponent()
		if err != nil {
			return err
		}

		c.TitleComponents = append(c.TitleComponents, vc)
	}

	return nil
}

type TypedObject struct {
	Config   jsoniter.RawMessage `json:"config,omitempty"`
	Metadata Metadata            `json:"metadata,omitempty"`
}

func (to *TypedObject) ToComponent() (Component, error) {
	o, err := unmarshal(*to)
	if err != nil {
		return nil, err
	}

	vc, ok := o.(Component)
	if !ok {
		return nil, errors.Errorf("unable to convert %T to Component",
			o)
	}

	return vc, nil
}

// Metadata collects common fields describing Components
type Metadata struct {
	Type     string           `json:"type"`
	Title    []TitleComponent `json:"title,omitempty"`
	Accessor string           `json:"accessor,omitempty"`
}

// SetTitleText sets the title using text components.
func (m *Metadata) SetTitleText(parts ...string) {
	var titleComponents []TitleComponent

	for _, part := range parts {
		titleComponents = append(titleComponents, NewText(part))
	}

	m.Title = titleComponents
}

// SetTitle sets the title using the provided components
func (m *Metadata) SetTitle(titleComponents []TitleComponent) {
	m.Title = titleComponents
}

func (m *Metadata) UnmarshalJSON(data []byte) error {
	x := struct {
		Type     string        `json:"type,omitempty"`
		Title    []TypedObject `json:"title,omitempty"`
		Accessor string        `json:"accessor,omitempty"`
	}{}

	if err := json.Unmarshal(data, &x); err != nil {
		return err
	}

	m.Type = x.Type
	m.Accessor = x.Accessor

	for _, title := range x.Title {
		vc, err := title.ToComponent()
		if err != nil {
			return errors.Wrap(err, "unmarshal-ing title")
		}

		tvc, ok := vc.(TitleComponent)
		if !ok {
			return errors.New("component in title isn't a title view component")
		}

		m.Title = append(m.Title, tvc)
	}

	return nil
}

// Component is a common interface for the data representation
// of visual components as rendered by the UI.
type Component interface {
	MarshalJSON() ([]byte, error)
	UnmarshalJSON([]byte) error

	// GetMetadata returns metadata for the component.
	GetMetadata() Metadata
	// GetMetadata sets the metadata for the component.
	SetMetadata(metadata Metadata)
	// SetAccessor sets the accessor for the component.
	SetAccessor(accessor string)
	// IsEmpty returns true if the component is "empty".
	IsEmpty() bool
	// String returns a string representation of the component.
	String() string
	// LessThan returns true if the components value is less than the other value.
	LessThan(other interface{}) bool
}

// TitleComponent is a view component that can be used for a title.
type TitleComponent interface {
	Component

	SupportsTitle()
}

// Title is a convenience method for creating a title.
func Title(components ...TitleComponent) []TitleComponent {
	return components
}

// TitleFromString is a convenience method for create a title from a string.
func TitleFromString(s string) []TitleComponent {
	return Title(NewText(s))
}

// TitleFromTitleComponent gets a title from a TitleComponent
func TitleFromTitleComponent(tc []TitleComponent) (string, error) {
	if len(tc) != 1 {
		return "", errors.New("exactly one title component can be converted")
	}
	return tc[0].String(), nil
}
